package net.yegong.matrix;

/**
 * @author cooper
 */
public class PermutationMatrix implements SpecialMatrix {
	public enum PermutationType {
		FROM_MAP, TO_MAP
	}

	public enum TargetType {
		COLUMNS, ROWS
	}

	public static final PermutationType FROM_MAP = PermutationType.FROM_MAP;
	public static final PermutationType TO_MAP = PermutationType.TO_MAP;
	public static final TargetType COLUMNS = TargetType.COLUMNS;
	public static final TargetType ROWS = TargetType.ROWS;

	/**
	 * To constract a Permutation you should be very carefully. <br />
	 * At the first of all, you must know which kind of data you have. The are
	 * two types, <b>to-map</b> and <b>from-map</b>. <br />
	 * Both of them should be int[]. If map is to-map, then the i-th column/row
	 * of source will be permuted to the map[i] -th column/row. If your map is
	 * from-map, then the j-th column/row of the destination matrix is from
	 * map[j].
	 * 
	 * @param pmap
	 * @param ptype
	 * @param target indicates that whether this action on COLUMNS or ROWS, this
	 *            only affects the {@link toMatrix} method. And can be changed
	 *            state by {@link switchTarget} method.
	 */
	public PermutationMatrix(int[] pmap, PermutationType ptype, TargetType target) {
		this.map = pmap;
		this.target = target;
		imap = new int[map.length];
		for (int i = 0; i < map.length; ++i) {
			imap[map[i]] = i;
		}
		if (ptype == TO_MAP) {
			int[] tmp = map;
			map = imap;
			imap = tmp;
		}
	}

	public SquareMatrix toMatrix() {
		SquareMatrix ret = new SquareMatrix(map.length);
		if (target == TargetType.COLUMNS) {
			for (int i = 0; i < map.length; ++i) {
				ret.set(map[i], i, 1);
			}
		}
		else {
			for (int i = 0; i < imap.length; ++i) {
				ret.set(imap[i], i, 1);
			}
		}
		return ret;
	}

	public PermutationMatrix transpose() {
		return new PermutationMatrix(map, TO_MAP, target);
	}

	public void transposeMe() {
		int[] tmp = map;
		map = imap;
		imap = tmp;
	}

	public void switchTarget() {
		if (target == COLUMNS)
			target = ROWS;
		else
			target = COLUMNS;
	}

	public int det() {
		int n = 0;
		for (int i = 0; i < map.length; ++i) {
			for (int j = 0; j < i; ++j) {
				if (map[j] > map[i])
					++n;
			}
		}
		return n % 2 == 0 ? 1 : -1;
	}

	/**
	 * P*A, but faster than m.rightMultiply(p.toMatrix())
	 * 
	 * @param m
	 * @return
	 */
	public Matrix rowsPermute(Matrix m) {
		int nRow = m.nRow;
		int nCol = m.nCol;
		if (nRow != map.length)
			throw new MatrixDimensionException();
		float[] array = m.array;
		float[] array2 = new float[nRow * nCol];
		for (int i = 0; i < nRow; ++i) {
			int f = fromWhich(i); // from which row
			for (int j = 0; j < nCol; ++j) {
				array2[i + j * nRow] = array[f + j * nRow];
			}
		}
		return new Matrix(array2, nRow, nCol, Matrix.FORTRAN);
	}

	public void rowsPermuteItSelf(Matrix m) {
		int nRow = m.nRow;
		int nCol = m.nCol;
		if (nRow != map.length)
			throw new MatrixDimensionException();
		float[] array = m.array;
		float[] array2 = new float[nRow * nCol];
		for (int i = 0; i < nRow; ++i) {
			int f = fromWhich(i); // from which row
			for (int j = 0; j < nCol; ++j) {
				array2[i + j * nRow] = array[f + j * nRow];
			}
		}
		m.array = array2;
		return;
	}

	/**
	 * A*P, but faster than m.leftMultiply(p.toMatrix())
	 * 
	 * @param m
	 * @return
	 */
	public Matrix columnsPermute(Matrix m) {
		int nRow = m.nRow;
		int nCol = m.nCol;
		if (nCol != map.length)
			throw new MatrixDimensionException();
		float[] array = m.array;
		float[] array2 = new float[nRow * nCol];
		for (int i = 0; i < nCol; ++i) {
			int f = fromWhich(i); // from which row
			System.arraycopy(array, f * nRow, array2, i * nRow, nRow);
		}
		return new Matrix(array2, nRow, nCol, Matrix.FORTRAN);
	}

	public void columnsPermuteItSelf(Matrix m) {
		int nRow = m.nRow;
		int nCol = m.nCol;
		if (nCol != map.length)
			throw new MatrixDimensionException();
		float[] array = m.array;
		float[] array2 = new float[nRow * nCol];
		for (int i = 0; i < nCol; ++i) {
			int f = fromWhich(i); // from which row
			System.arraycopy(array, f * nRow, array2, i * nRow, nRow);
		}
		m.array = array2;
		return;
	}

	@Override
	public PermutationMatrix clone() {
		return new PermutationMatrix(MatrixHelper.copy(map), FROM_MAP, target);
	}

	public int get(int i, int j) {
		if (map[j] == i)
			return 1;
		return 0;
	}

	@Override
	public String toString() {
		StringBuilder buf = new StringBuilder();
		for (int i = 0; i < map.length; ++i) {
			for (int j = 0; j < map.length; ++j) {
				buf.append(get(i, j));
				buf.append(", ");
			}
			buf.append("\n");
		}
		return buf.toString();
	}

	public int fromWhich(int i) {
		return map[i];
	}

	public int toWhich(int i) {
		return imap[i];
	}

	protected TargetType target;
	protected int[] map = null;
	protected int[] imap = null;
}
